package cn.wolfcode.service.impl;

import cn.wolfcode.common.constants.CommonConstants;
import cn.wolfcode.common.domain.UserInfo;
import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.common.web.Result;
import cn.wolfcode.common.web.anno.RequireLogin;
import cn.wolfcode.domain.*;
import cn.wolfcode.feign.AlipayFeignApi;
import cn.wolfcode.feign.IntergralFeignApi;
import cn.wolfcode.mapper.OrderInfoMapper;
import cn.wolfcode.mapper.PayLogMapper;
import cn.wolfcode.mapper.RefundLogMapper;
import cn.wolfcode.mapper.SeckillProductMapper;
import cn.wolfcode.mq.MQConstant;
import cn.wolfcode.mq.OrderMQResult;
import cn.wolfcode.redis.SeckillRedisKey;
import cn.wolfcode.service.IOrderInfoService;
import cn.wolfcode.service.ISeckillProductService;
import cn.wolfcode.util.DateUtil;
import cn.wolfcode.util.IdGenerateUtil;
import cn.wolfcode.util.UserUtil;
import cn.wolfcode.web.msg.SeckillCodeMsg;
import com.alibaba.fastjson.JSON;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by wolfcode-lanxw
 *
 * Seata AT 模式, 业务无侵入, 事务发起方@GlobalTransactional, 被调用方@Transactional 即可
 * 依赖 undo_log 表, 非关系型数据库就不适合用AT模式
 */
@Service
public class OrderInfoSeviceImpl implements IOrderInfoService {
    @Autowired
    private ISeckillProductService seckillProductService;
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private PayLogMapper payLogMapper;
    @Autowired
    private RefundLogMapper refundLogMapper;
    @Autowired
    private SeckillProductMapper seckillProductMapper;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private AlipayFeignApi alipayFeignApi;
    @Autowired
    private IntergralFeignApi intergralFeignApi;

    /**
     * 秒杀下单
     * @param seckillProductVo
     * @param userInfo
     * @return
     */
    @Override
    @Transactional
    public String doSeckill(SeckillProductVo seckillProductVo, UserInfo userInfo) {
        // 用 UUID mysql 性能不好优化, 因为过于无规则; 这里采用有规律的雪华算法
        String orderNo = String.valueOf(IdGenerateUtil.get().nextId());

        // 6_1. 创建订单
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setUserId(userInfo.getPhone());
        orderInfo.setProductId(seckillProductVo.getProductId());
        orderInfo.setProductName(seckillProductVo.getProductName());
        orderInfo.setProductImg(seckillProductVo.getProductImg());
        orderInfo.setProductCount(seckillProductVo.getStockCount());
        orderInfo.setProductPrice(seckillProductVo.getProductPrice());
        orderInfo.setSeckillPrice(seckillProductVo.getSeckillPrice());
        orderInfo.setIntergral(seckillProductVo.getIntergral());
        orderInfo.setCreateDate(new Date());
        orderInfo.setSeckillDate(new Date());
        orderInfo.setSeckillTime(seckillProductVo.getTime());
        orderInfo.setSeckillId(seckillProductVo.getId());
        orderInfo.setOrderNo(orderNo);

        // 6_3. 存入 t_order_info                 // 此处利用mysql唯一索引防止多线程多次下单
        orderInfoMapper.insert(orderInfo);
        // 6_2. 减少商品库存 t_seckill_product stock_count
        int i = seckillProductMapper.decrStock(seckillProductVo.getId());
        if (i <= 0)
            throw new BusinessException(SeckillCodeMsg.SECKILL_STOCK_OVER);

//        // 将 数据库操作 尽可能封装, 减少锁占用时间
//        reduceStockAndAddOrder(seckillProductVo, orderInfo);

        // 6_4. redis中创建用户下单标记 (配合5), 这种只能防止单线程下单; 对于一个用户多线程下单, 采用mysql唯一索引的特性, 建立user_id和seckill_id字段组合的唯一索引
        // 添加  set :  key=pre:time, value=phone:seckillId
        redisTemplate.opsForSet().add(SeckillRedisKey.SECKILL_ORDER_SET.getRealKey(String.valueOf(seckillProductVo.getTime())),
                userInfo.getPhone() + ":" + seckillProductVo.getId());
        // 因为设置了组合唯一索引, 因此这一步就是为了普通用户提前判断是否下单成功, 少一些业务逻辑(少了一条sql调整), 也是redis空间换时间
        return orderNo;
    }

    // spring 事务陷阱, @Transactional 只有代理调用才能生效, 方法内调用是使用this本对象调用, 无法生效.
//    @Transactional
//    public void reduceStockAndAddOrder(SeckillProductVo seckillProductVo, OrderInfo orderInfo) {
//        // 6_2. 减少商品库存 t_seckill_product stock_count
//        int i = seckillProductMapper.decrStock(seckillProductVo.getId());
//        if (i <= 0)
//            throw new BusinessException(SeckillCodeMsg.SECKILL_STOCK_OVER);
//        // 6_3. 存入 t_order_info
//        orderInfoMapper.insert(orderInfo);
//    }


    @Override
    @Transactional
    public void cancelTimeOutOrder(OrderMQResult orderMQResult) {
        // 1. 判断订单状态是否为 待支付
        OrderInfo orderInfo = orderInfoMapper.find(orderMQResult.getOrderNo());
        if (orderInfo != null && orderInfo.getStatus() == OrderInfo.STATUS_ARREARAGE) {
            // 2. 修改状态为 超时取消
            orderInfoMapper.updateCancelStatus(orderInfo.getOrderNo(), OrderInfo.STATUS_TIMEOUT);
            // 3. 回补 mysql 真实库存
            seckillProductMapper.incrStock(orderInfo.getSeckillId());
            // 4. 回补 redis 库存, 以及修改本地标识
            seckillProductService.replenishRedisStock(orderMQResult);
        }
    }

    @Override
    public OrderInfo find(String orderNo) {
//        return orderInfoMapper.find(orderNo);
        // 改用 redis 缓存中的数据
        Object o = redisTemplate.opsForHash().get(SeckillRedisKey.SECKILL_ORDER_HASH.getRealKey(""), orderNo);
        if (o == null)
            return null;
        return JSON.parseObject(String.valueOf(o), OrderInfo.class);
    }


    @Value("${pay.returnUrl}")
    private String returnUrl;
    @Value("${pay.notifyUrl}")
    private String notifyUrl;

    @Override
    public String payOnline(String orderNo) {
        OrderInfo orderInfo = this.find(orderNo);
        PayVo payVo = new PayVo();

        payVo.setOutTradeNo(orderNo);
        payVo.setTotalAmount(orderInfo.getSeckillPrice().toString());
        payVo.setSubject(orderInfo.getProductName());
        payVo.setBody(orderInfo.getProductName());
        payVo.setReturnUrl(returnUrl);
        payVo.setNotifyUrl(notifyUrl);

        // 远程调用支付服务
        Result<String> stringResult = alipayFeignApi.payOnline(payVo);
        if (stringResult != null && !stringResult.hasError())
            return stringResult.getData();

        return null;
    }

    @Override
    @Transactional
    public void paySuccess(String orderNo) {
        OrderInfo orderInfo = find(orderNo);

        PayLog payLog = new PayLog();
        payLog.setTradeNo(orderNo);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        payLog.setNotifyTime(dateFormat.format(new Date()));
        payLog.setPayType(OrderInfo.PAYTYPE_ONLINE);
        payLog.setTotalAmount(String.valueOf(orderInfo.getSeckillPrice()));
        payLogMapper.insert(payLog);

        orderInfoMapper.changePayStatus(orderNo, OrderInfo.STATUS_ACCOUNT_PAID, OrderInfo.PAYTYPE_ONLINE);
    }

    @Override
    @Transactional
    public void refundOnline(OrderInfo orderInfo) {
        RefundLog refundLog = new RefundLog();
        refundLog.setOutTradeNo(orderInfo.getOrderNo());
        refundLog.setRefundAmount(orderInfo.getSeckillPrice().toString());
        refundLog.setRefundTime(new Date());
        refundLog.setRefundType(OrderInfo.PAYTYPE_ONLINE);
        refundLog.setRefundReason("用户取消订单");
        refundLogMapper.insert(refundLog);

        orderInfoMapper.changeRefundStatus(orderInfo.getOrderNo(), OrderInfo.STATUS_REFUND);

        RefundVo refundVo = new RefundVo();
        refundVo.setOutTradeNo(orderInfo.getOrderNo());
        refundVo.setRefundReason("用户取消订单");
        refundVo.setRefundAmount(orderInfo.getSeckillPrice().toString());
        Result<Boolean> booleanResult = alipayFeignApi.refundOnline(refundVo);
        if (booleanResult != null && !booleanResult.hasError()) {
            if (!booleanResult.getData())
                throw new BusinessException(SeckillCodeMsg.REFUND_ERROR);
            orderInfoMapper.changeRefundStatus(orderInfo.getOrderNo(), OrderInfo.STATUS_REFUND);

            // 回补库存
            seckillProductMapper.incrStock(orderInfo.getSeckillId());
            OrderMQResult orderMQResult = new OrderMQResult();
            orderMQResult.setSeckillId(orderInfo.getSeckillId());
            orderMQResult.setTime(orderInfo.getSeckillTime());
            seckillProductService.replenishRedisStock(orderMQResult);
        }

    }

    @Override
    @GlobalTransactional
    public void payIntergral(String orderNo) {
        OrderInfo orderInfo = find(orderNo);

        // 支付日志
        PayLog payLog = new PayLog();
        payLog.setTradeNo(orderNo);
        payLog.setNotifyTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        payLog.setPayType(OrderInfo.PAYTYPE_INTERGRAL);
        payLog.setTotalAmount(String.valueOf(orderInfo.getIntergral()));
        payLogMapper.insert(payLog);

        // 积分服务, 减少积分
        OperateIntergralVo operateIntergralVo = new OperateIntergralVo();
        operateIntergralVo.setPk(orderNo);
        operateIntergralVo.setUserId(orderInfo.getUserId());
        operateIntergralVo.setValue(orderInfo.getIntergral());
        operateIntergralVo.setInfo(orderInfo.getProductName());
        Result<Boolean> booleanResult = intergralFeignApi.decrIntergral(operateIntergralVo);

        if (booleanResult == null || booleanResult.hasError())
            throw new BusinessException(SeckillCodeMsg.INTERGRAL_SERVER_ERROR);

        // 修改订单状态
        orderInfoMapper.changePayStatus(orderNo, OrderInfo.STATUS_ACCOUNT_PAID, OrderInfo.PAYTYPE_INTERGRAL);

//        int a = 1/0;
    }

    @Override
    @GlobalTransactional
    public void refundIntergral(OrderInfo orderInfo) {
        RefundLog refundLog = new RefundLog();
        refundLog.setOutTradeNo(orderInfo.getOrderNo());
        refundLog.setRefundAmount(orderInfo.getIntergral().toString());
        refundLog.setRefundTime(new Date());
        refundLog.setRefundType(OrderInfo.PAYTYPE_INTERGRAL);
        refundLog.setRefundReason("用户取消订单");
        refundLogMapper.insert(refundLog);

        OperateIntergralVo operateIntergralVo = new OperateIntergralVo();
        operateIntergralVo.setPk(orderInfo.getOrderNo());
        operateIntergralVo.setUserId(orderInfo.getUserId());
        operateIntergralVo.setValue(orderInfo.getIntergral());
        operateIntergralVo.setInfo(orderInfo.getProductName());

        Result<Boolean> booleanResult = intergralFeignApi.incrIntergral(operateIntergralVo);
        if (booleanResult == null || booleanResult.hasError())
            throw new BusinessException(SeckillCodeMsg.INTERGRAL_SERVER_ERROR);

        orderInfoMapper.changeRefundStatus(orderInfo.getOrderNo(), OrderInfo.STATUS_REFUND);

        // 回补库存
        seckillProductMapper.incrStock(orderInfo.getSeckillId());
        OrderMQResult orderMQResult = new OrderMQResult();
        orderMQResult.setSeckillId(orderInfo.getSeckillId());
        orderMQResult.setTime(orderInfo.getSeckillTime());
        seckillProductService.replenishRedisStock(orderMQResult);

    }
}
